/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.tomcat.util.net;

import java.net.InetAddress;
import java.util.concurrent.Executor;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;

/**
 * APR tailored thread pool, providing the following services:
 * <ul>
 * <li>Socket acceptor thread</li>
 * <li>Socket poller thread</li>
 * <li>Sendfile thread</li>
 * <li>Worker threads pool</li>
 * </ul>
 * 
 * When switching to Java 5, there's an opportunity to use the virtual machine's
 * thread pool.
 * 
 * @author Mladen Turk
 * @author Remy Maucherat
 */
public abstract class BaseEndpoint {

	// -------------------------------------------------------------- Constants

	protected static Log log = LogFactory.getLog(BaseEndpoint.class);

	protected static StringManager sm = StringManager
			.getManager("org.apache.tomcat.util.net.res");

	/**
	 * The Request attribute key for the cipher suite.
	 */
	public static final String CIPHER_SUITE_KEY = "javax.servlet.request.cipher_suite";

	/**
	 * The Request attribute key for the key size.
	 */
	public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size";

	/**
	 * The Request attribute key for the client certificate chain.
	 */
	public static final String CERTIFICATE_KEY = "javax.servlet.request.X509Certificate";

	/**
	 * The Request attribute key for the session id. This one is a Tomcat
	 * extension to the Servlet spec.
	 */
	public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session";

	// ----------------------------------------------------------------- Fields

	/**
	 * Running state of the endpoint.
	 */
	protected volatile boolean running = false;

	/**
	 * Will be set to true whenever the endpoint is paused.
	 */
	protected volatile boolean paused = false;

	/**
	 * Track the initialization state of the endpoint.
	 */
	protected boolean initialized = false;

	/**
	 * Current worker threads busy count.
	 */
	protected int curThreadsBusy = 0;

	/**
	 * Current worker threads count.
	 */
	protected int curThreads = 0;

	/**
	 * Sequence number used to generate thread names.
	 */
	protected int sequence = 0;

	// ------------------------------------------------------------- Properties

	/**
	 * External Executor based thread pool.
	 */
	protected Executor executor = null;

	public void setExecutor(Executor executor) {
		this.executor = executor;
	}

	public Executor getExecutor() {
		return executor;
	}

	/**
	 * Maximum amount of worker threads.
	 */
	protected int maxThreads = 40;

	public void setMaxThreads(int maxThreads) {
		this.maxThreads = maxThreads;
	}

	public int getMaxThreads() {
		return maxThreads;
	}

	/**
	 * Priority of the acceptor and poller threads.
	 */
	protected int threadPriority = Thread.NORM_PRIORITY;

	public void setThreadPriority(int threadPriority) {
		this.threadPriority = threadPriority;
	}

	public int getThreadPriority() {
		return threadPriority;
	}

	/**
	 * Server socket port.
	 */
	protected int port;

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	/**
	 * Address for the server socket.
	 */
	protected InetAddress address;

	public InetAddress getAddress() {
		return address;
	}

	public void setAddress(InetAddress address) {
		this.address = address;
	}

	/**
	 * Allows the server developer to specify the backlog that should be used
	 * for server sockets. By default, this value is 100.
	 */
	protected int backlog = 100;

	public void setBacklog(int backlog) {
		if (backlog > 0)
			this.backlog = backlog;
	}

	public int getBacklog() {
		return backlog;
	}

	/**
	 * Socket TCP no delay.
	 */
	protected boolean tcpNoDelay = false;

	public boolean getTcpNoDelay() {
		return tcpNoDelay;
	}

	public void setTcpNoDelay(boolean tcpNoDelay) {
		this.tcpNoDelay = tcpNoDelay;
	}

	/**
	 * Socket linger.
	 */
	protected int soLinger = 100;

	public int getSoLinger() {
		return soLinger;
	}

	public void setSoLinger(int soLinger) {
		this.soLinger = soLinger;
	}

	/**
	 * Socket timeout.
	 */
	protected int soTimeout = -1;

	public int getSoTimeout() {
		return soTimeout;
	}

	public void setSoTimeout(int soTimeout) {
		this.soTimeout = soTimeout;
	}

	/**
	 * The default is true - the created threads will be in daemon mode. If set
	 * to false, the control thread will not be daemon - and will keep the
	 * process alive.
	 */
	protected boolean daemon = true;

	public void setDaemon(boolean b) {
		daemon = b;
	}

	public boolean getDaemon() {
		return daemon;
	}

	/**
	 * Name of the thread pool, which will be used for naming child threads.
	 */
	protected String name = "TP";

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	/**
	 * Dummy maxSpareThreads property.
	 */
	public int getMaxSpareThreads() {
		return 0;
	}

	/**
	 * Dummy minSpareThreads property.
	 */
	public int getMinSpareThreads() {
		return 0;
	}

	// --------------------------------------------------------- Public Methods

	/**
	 * Return the amount of threads that are managed by the pool.
	 * 
	 * @return the amount of threads that are managed by the pool
	 */
	public int getCurrentThreadCount() {
		return curThreads;
	}

	/**
	 * Return the amount of threads currently busy.
	 * 
	 * @return the amount of threads currently busy
	 */
	public int getCurrentThreadsBusy() {
		return curThreadsBusy;
	}

	/**
	 * Return the state of the endpoint.
	 * 
	 * @return true if the endpoint is running, false otherwise
	 */
	public boolean isRunning() {
		return running;
	}

	/**
	 * Return the state of the endpoint.
	 * 
	 * @return true if the endpoint is paused, false otherwise
	 */
	public boolean isPaused() {
		return paused;
	}

	// ----------------------------------------------- Public Lifecycle Methods

	/**
	 * Initialize the endpoint.
	 */
	public abstract void init() throws Exception;

	/**
	 * Start the APR endpoint, creating acceptor, poller and sendfile threads.
	 */
	public abstract void start() throws Exception;

	/**
	 * Pause the endpoint, which will make it stop accepting new sockets.
	 */
	public void pause() {
		if (running && !paused) {
			paused = true;
			unlockAccept();
		}
	}

	/**
	 * Resume the endpoint, which will make it start accepting new sockets
	 * again.
	 */
	public void resume() {
		if (running) {
			paused = false;
		}
	}

	/**
	 * Stop the endpoint. This will cause all processing threads to stop.
	 */
	public abstract void stop();

	/**
	 * Deallocate APR memory pools, and close server socket.
	 */
	public abstract void destroy() throws Exception;

	// ------------------------------------------------------ Protected Methods

	/**
	 * Get a sequence number used for thread naming.
	 */
	protected int getSequence() {
		return sequence++;
	}

	/**
	 * Unlock the server socket accept using a bugus connection.
	 */
	protected void unlockAccept() {
		java.net.Socket s = null;
		try {
			// Need to create a connection to unlock the accept();
			if (address == null) {
				s = new java.net.Socket(InetAddress.getByName("localhost")
						.getHostAddress(), port);
			} else {
				s = new java.net.Socket(address, port);
				// setting soLinger to a small value will help shutdown the
				// connection quicker
				s.setSoLinger(true, 0);
			}
		} catch (Exception e) {
			if (log.isDebugEnabled()) {
				log.debug(sm.getString("endpoint.debug.unlock", "" + port), e);
			}
		} finally {
			if (s != null) {
				try {
					s.close();
				} catch (Exception e) {
					// Ignore
				}
			}
		}
	}

}
